package net.qiqbframework.eventhandling;


import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.messaging.annotation.MessageHandlingMemberFactory;
import net.qiqbframework.messaging.annotation.HandlerParameterResolverFactory;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static net.qiqbframework.common.BuilderUtils.assertNonNull;
import static net.qiqbframework.common.BuilderUtils.assertThat;
import static java.util.Arrays.asList;


public class SimpleEventHandlerInvoker implements EventMessageHandler {

    private final List<EventMessageHandler> eventHandlingComponents;
    private MessageHandlingMemberFactory handlerDefinition;
    private final ListenerInvocationErrorHandler listenerInvocationErrorHandler;

    protected SimpleEventHandlerInvoker(Builder<?> builder) {
        builder.validate();
        this.eventHandlingComponents =
                builder.eventHandlers.stream()
                        .map(handler -> handler instanceof EventMessageHandler
                                ? (EventMessageHandler) handler
                                : builder.wrapEventMessageHandler(handler)
                        )
                        .collect(Collectors.toCollection(ArrayList::new));

        this.listenerInvocationErrorHandler = builder.listenerInvocationErrorHandler;
    }

    /**
     * Checks if a {@link List} has been passed as first parameter. It is a common 'mistake', which is detected and
     * fixed here.
     *
     * @param eventHandlers the event handlers to check whether it contains a {@link List}
     * @return a {@link List} of events handlers
     */
    private static List<?> detectList(Object[] eventHandlers) {
        return eventHandlers.length == 1 && (eventHandlers[0] instanceof List)
                ? (List<?>) eventHandlers[0]
                : asList(eventHandlers);
    }

    public static <B extends Builder<?>> Builder<B> builder() {
        return new Builder<>();
    }

    /**
     * Gets the list of {@link EventMessageHandler} delegates in this invoker. These delegates are the end point of
     * event handling.
     *
     * @return The list of {@link EventMessageHandler} delegates.
     */
    public List<EventMessageHandler> eventHandlers() {
        return Collections.unmodifiableList(eventHandlingComponents);
    }

    @Override
    public Object handle(@Nonnull EventMessage<?> message) throws Exception {

        invokeHandlers(message);
        return null;
    }


    protected void invokeHandlers(EventMessage<?> message) throws Exception {
        for (EventMessageHandler handler : eventHandlingComponents) {
            try {
                handler.handle(message);
            } catch (Exception e) {
                listenerInvocationErrorHandler.onError(e, message, handler);
            }
        }
    }





    public static class Builder<B extends Builder<?>> {

        private List<?> eventHandlers;
        private HandlerParameterResolverFactory parameterResolverFactory;
        private MessageHandlingMemberFactory handlerDefinition;
        private ListenerInvocationErrorHandler listenerInvocationErrorHandler = new LoggingErrorHandler();

        public B eventHandlers(Object... eventHandlers) {
            return eventHandlers(detectList(eventHandlers));
        }

        public B eventHandlers(@Nonnull List<?> eventHandlers) {
            assertThat(eventHandlers,
                    list -> list != null && !list.isEmpty(),
                    "At least one EventMessageHandler should be provided");
            this.eventHandlers = eventHandlers;
            // noinspection unchecked
            return (B) this;
        }


        public B parameterResolverFactory(@Nonnull HandlerParameterResolverFactory parameterResolverFactory) {
            assertNonNull(parameterResolverFactory, "ParameterResolverFactory may not be null");
            this.parameterResolverFactory = parameterResolverFactory;
            // noinspection unchecked
            return (B) this;
        }


        public B handlerDefinition(@Nonnull MessageHandlingMemberFactory handlerDefinition) {
            assertNonNull(handlerDefinition, "HandlerDefinition may not be null");
            this.handlerDefinition = handlerDefinition;
            // noinspection unchecked
            return (B) this;
        }


        public B listenerInvocationErrorHandler(
                @Nonnull ListenerInvocationErrorHandler listenerInvocationErrorHandler
        ) {
            assertNonNull(listenerInvocationErrorHandler, "ListenerInvocationErrorHandler may not be null");
            this.listenerInvocationErrorHandler = listenerInvocationErrorHandler;
            // noinspection unchecked
            return (B) this;
        }


        /**
         * Initializes a {@link SimpleEventHandlerInvoker} as specified through this Builder.
         *
         * @return a {@link SimpleEventHandlerInvoker} as specified through this Builder
         */
        public SimpleEventHandlerInvoker build() {
            return new SimpleEventHandlerInvoker(this);
        }


        public AnnotationEventHandlerAdapter wrapEventMessageHandler(@Nonnull Object eventHandler) {
            if (parameterResolverFactory != null && handlerDefinition == null) {
                return new AnnotationEventHandlerAdapter(eventHandler, parameterResolverFactory);
            } else {
                return new AnnotationEventHandlerAdapter(eventHandler, parameterResolverFactory, (e, h) -> h, handlerDefinition);
            }
        }


        protected void validate() throws ConfigurationException {
            assertThat(eventHandlers,
                    list -> list != null && !list.isEmpty(),
                    "At least one EventMessageHandler should be provided");
        }
    }
}
